#include<core.p4>
#include<v1model.p4>

const bit<16> TYPE_IPV4 = 0x800;
const bit<16> TYPE_IPV6 = 0x86DD;

/*HEADERS*/

typedef bit<9>  egressSpec_t;
typedef bit<48> macAddr_t;
typedef bit<32> ip4Addr_t;
typedef bit<128> ip6Addr_t;


header ethernet_t{
	macAddr_t dstAddr;
	macAddr_t srcAddr;
	bit<16> etherType;
}

header ipv4_t {
    bit<4>    version;
    bit<4>    ihl;
    bit<8>    diffserv;
    bit<16>   totalLen;
    bit<16>   identification;
    bit<3>    flags;
    bit<13>   fragOffset;
    bit<8>    ttl;
    bit<8>    protocol;
    bit<16>   hdrChecksum;
    ip4Addr_t srcAddr;
    ip4Addr_t dstAddr;
}

header ipv6_t{
	bit<4> version;
	bit<8> trafficClass;
	bit<20> flowLabel;
	bit<16> payLoadLen;
	bit<8> nextHdr;
	bit<8> hopLimit;
	bit<128> srcAddr;
	bit<128> dstAddr;
}

struct metadata{

}

struct headers{
	ethernet_t ethernet;
	ipv4_t ipv4;
	ipv6_t ipv6;
}

/*PARSER*/

parser MyParser(packet_in packet,out headers hdr,inout metadata meta,inout standard_metadata_t standard_metadata){
	state start{
		transition parse_ethernet;
	}

	state parse_ethernet{
		packet.extract(hdr.ethernet);
		transition select(hdr.ethernet.etherType){
			TYPE_IPV4: parse_ipv4;
			TYPE_IPV6: parse_ipv6;
			default:accept;
		}
	}

	state parse_ipv4 {
			packet.extract(hdr.ipv4);
			transition accept;
	}

	state parse_ipv6{
		packet.extract(hdr.ipv6);
		transition accept;
	}
}

/*CHECKSUM VERIFICATION*/

control MyVerifyChecksum(inout headers hdr,inout metadata meta){
	apply{}
}

/*INGRESS PROCESSING*/

control MyIngress(inout headers hdr,inout metadata meta,inout standard_metadata_t standard_metadata){
	action drop(){
		mark_to_drop(standard_metadata);
	}

	action ipv4_forward(macAddr_t dstAddr,egressSpec_t port) {
			standard_metadata.egress_spec = port;
			hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
			hdr.ethernet.dstAddr = dstAddr;
			hdr.ipv4.ttl = hdr.ipv4.ttl-1;

	}

	action ipv6_forward(macAddr_t dstAddr,egressSpec_t port){
		standard_metadata.egress_spec = port;
		hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
		hdr.ethernet.dstAddr = dstAddr;
		hdr.ipv6.hopLimit = hdr.ipv6.hopLimit - 1;
	}

	table ipv4_lpm {
			key = {
					hdr.ipv4.dstAddr: lpm;
			}
			actions = {
					ipv4_forward;
					drop;
					NoAction;
			}
			size = 1024;
			default_action = NoAction();
	}

	table ipv6_lpm{
		key = {
			hdr.ipv6.dstAddr: lpm;
		}

		actions = {
			ipv6_forward;
			drop;
			NoAction;
		}

		size = 1024;

		default_action = drop();
	}

	apply{
		if (hdr.ipv4.isValid()){
			 ipv4_lpm.apply();
		}
		else if(hdr.ipv6.isValid()){
			ipv6_lpm.apply();
		}
	}
}

/*EGRESS PROCESSING*/

control MyEgress(inout headers hdr,inout metadata meta,inout standard_metadata_t standard_metadata){
	apply{}
}

/*CHECKSUM COMPUTATION*/

control MyComputeChecksum(inout headers hdr,inout metadata meta){
		apply {
			 update_checksum(
					 hdr.ipv4.isValid(),
					 { hdr.ipv4.version,
						 hdr.ipv4.ihl,
						 hdr.ipv4.diffserv,
						 hdr.ipv4.totalLen,
						 hdr.ipv4.identification,
						 hdr.ipv4.flags,
						 hdr.ipv4.fragOffset,
						 hdr.ipv4.ttl,
						 hdr.ipv4.protocol,
						 hdr.ipv4.srcAddr,
						 hdr.ipv4.dstAddr },
					 hdr.ipv4.hdrChecksum,
					 HashAlgorithm.csum16);
		}
}

/*DEPARSER*/

control MyDeparser(packet_out packet,in headers hdr){
	apply{
		packet.emit(hdr.ethernet);
		packet.emit(hdr.ipv4);
		packet.emit(hdr.ipv6);
	}
}

/*SWITCH*/

V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
)main;
